﻿#region using
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using Jsil.Runtime;
#endregion using

namespace Jsil.Compiler
{
	class RuntimeCopier
	{
		#region fields
		ModuleBuilder _modBldr;
		Assembly _asmToCopy;
		Dictionary<Type,TypeBuilder> _copiedTypes=new Dictionary<Type,TypeBuilder>();
		Dictionary<MethodInfo,MethodBuilder> _copiedMethods=new Dictionary<MethodInfo,MethodBuilder>();
		Dictionary<ConstructorInfo,ConstructorBuilder> _copiedConstructors=new Dictionary<ConstructorInfo,ConstructorBuilder>();
		Dictionary<FieldInfo,FieldBuilder> _copiedFields=new Dictionary<FieldInfo,FieldBuilder>();
		Dictionary<EventInfo,EventBuilder> _copiedEvents=new Dictionary<EventInfo,EventBuilder>();
		Dictionary<PropertyInfo,PropertyBuilder> _copiedProperties=new Dictionary<PropertyInfo,PropertyBuilder>();
		Dictionary<Type,List<Type>> _dependsOn=new Dictionary<Type,List<Type>>();
		#endregion fields

		bool phase2=false;
		bool done=false;
		internal RuntimeCopier(Assembly asmToCopy,ModuleBuilder moduleBuilder)
		{
			_asmToCopy=asmToCopy;
			_modBldr=moduleBuilder;
		}

		#region explore
		static List<Type> ExploreType(Type type)
		{
			List<Type> result=new List<Type>();
			foreach (ConstructorInfo item in type.GetConstructors(_bindingFlags))
				AnalyzeConstructor(result,item);

			foreach (FieldInfo item in type.GetFields(_bindingFlags))
				AnalyzeField(result,item);

			foreach (EventInfo item in type.GetEvents(_bindingFlags))
			{
				AnalyzeMethod(result,item.GetAddMethod());
				AnalyzeMethod(result,item.GetRemoveMethod());
				AnalyzeMethod(result,item.GetRaiseMethod());
				foreach (MethodInfo item2 in item.GetOtherMethods())
					AnalyzeMethod(result,item2);
			}

			foreach (PropertyInfo item in type.GetProperties(_bindingFlags))
			{
				AnalyzeType(result,item.PropertyType);
				AnalyzeMethod(result,item.GetGetMethod(true));
				AnalyzeMethod(result,item.GetSetMethod(true));
			}

			foreach (MethodInfo item in type.GetMethods(_bindingFlags))
				AnalyzeMethod(result,item);

			if (result.Contains(type))
				result.Remove(type);
			for (int a=result.Count-1;a>=0;a--)
			{
				Type item=result[a];
				if (item.DeclaringType==type)
					result.RemoveAt(a);
			}

			return result;
		}

		static void AnalyzeConstructorHeader(List<Type> depends,ConstructorInfo constructor)
		{
			AnalyzeParameters(depends,constructor);
		}

		static void AnalyzeConstructor(List<Type> depends,ConstructorInfo constructor)
		{
			AnalyzeParameters(depends,constructor);
			AnalyzeMethodBody(depends,constructor);
		}

		static void AnalyzeField(List<Type> depends,FieldInfo field)
		{
			AnalyzeType(depends,field.FieldType);
		}

		static void AnalyzeMethodHeader(List<Type> depends,MethodInfo method)
		{
			AnalyzeParameters(depends,method);
		}

		static void AnalyzeMethod(List<Type> depends,MethodInfo method)
		{
			if (method==null)
				return;
			AnalyzeMethodHeader(depends,method);
			AnalyzeMethodBody(depends,method);
		}

		static void AnalyzeType(List<Type> depends,Type type)
		{
			if (type==null)
				return;

			bool isGenericNonDefType=(type.IsGenericType)&&(!type.IsGenericTypeDefinition);
			bool isByRefType=(!type.IsGenericType)&&(!type.IsGenericTypeDefinition)&&(type.IsByRef);
			bool breakRuntimeBlock=isGenericNonDefType||isByRefType;
			bool isRuntimeClass=type.Assembly.GetName().Name=="Jsil.Runtime";

			if ((!breakRuntimeBlock)&&(!isRuntimeClass))
				return;

			#region special types
			if (type.IsGenericParameter)
			{
				AnalyzeType(depends,type.DeclaringType);
				foreach (Type item in type.GetGenericArguments())
					AnalyzeType(depends,item);
				return;
			}

			if (type.IsArray)
			{
				AnalyzeType(depends,type.GetElementType());
				return;
			}

			if (isGenericNonDefType)
			{
				AnalyzeType(depends,type.GetGenericTypeDefinition());
				foreach (Type item in type.GetGenericArguments())
					AnalyzeType(depends,item);
				return;
			}

			if (isByRefType)
			{
				AnalyzeType(depends,type.GetElementType());
				return;
			}
			#endregion special types

			foreach (Type item in type.GetInterfaces())
				AnalyzeType(depends,item);

			Type baseType=typeof(object);
			if (type.BaseType!=baseType)
				AnalyzeType(depends,type.BaseType);

			if ((isRuntimeClass)&&(!depends.Contains(type)))
				depends.Add(type);
		}

		static void AnalyzeMethodBody(List<Type> depends,MethodBase method)
		{
			MethodBody methodBody=method.GetMethodBody();
			if (methodBody==null)
				return;

			IlReader.IlInstruction[] instructions=new IlReader().ReadMethod(methodBody,method);
			foreach (IlReader.IlInstruction instruction in instructions)
			{
				if (instruction.Data is FieldInfo)
					AnalyzeField(depends,(FieldInfo)instruction.Data);
				else if (instruction.Data is MethodInfo)
					AnalyzeMethodHeader(depends,(MethodInfo)instruction.Data);
				else if (instruction.Data is Type)
					AnalyzeType(depends,(Type)instruction.Data);
				else if (instruction.Data is ConstructorInfo)
					AnalyzeConstructorHeader(depends,(ConstructorInfo)instruction.Data);
			}
		}

		static void AnalyzeParameters(List<Type> depends,MethodBase method)
		{
			foreach (ParameterInfo item in method.GetParameters())
				AnalyzeType(depends,item.ParameterType);
		}
		#endregion explore

		#region copy
		static BindingFlags _bindingFlags=BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Instance|BindingFlags.Static|BindingFlags.DeclaredOnly;
		internal void Copy()
		{
			if (done)
				return;
			done=true;

			Assembly asm=typeof(GlobalObject).Assembly;
			Type[] types=asm.GetTypes();
			foreach (Type item in types)
			{
				_dependsOn.Add(item,ExploreType(item));
				CopyTypeDefinition(item);
			}

			foreach (KeyValuePair<Type,TypeBuilder> item in _copiedTypes)
				foreach (FieldInfo item2 in item.Key.GetFields(_bindingFlags))
					CopyField(item2,item.Value);

			foreach (KeyValuePair<Type,TypeBuilder> item in _copiedTypes)
				foreach (PropertyInfo item2 in item.Key.GetProperties(_bindingFlags))
					CopyPropertyDefinition(item2,item.Value);

			//events

			foreach (KeyValuePair<Type,TypeBuilder> item in _copiedTypes)
				foreach (ConstructorInfo item2 in item.Key.GetConstructors(_bindingFlags))
					CopyConstructorDefinition(item2,item.Value);

			foreach (KeyValuePair<Type,TypeBuilder> item in _copiedTypes)
				foreach (MethodInfo item2 in item.Key.GetMethods(_bindingFlags))
					CopyMethodDefinition(item2,item.Value);

			phase2=true;
			FinalizeSpecialTypes();
			foreach (KeyValuePair<ConstructorInfo,ConstructorBuilder> item in _copiedConstructors)
				CopyConstructor(item);

			foreach (KeyValuePair<MethodInfo,MethodBuilder> item in _copiedMethods)
				CopyMethod(item);

			foreach (KeyValuePair<Type,TypeBuilder> item in _copiedTypes)
				CreateType(item.Key);
		}

		bool IsSpecialType(Type type)
		{
			return type.IsGenericTypeDefinition;
		}

		Dictionary<Type,Type> _createdTypes=new Dictionary<Type,Type>();
		Type CreateType(Type typeToCreateOrig)
		{
			if (!IsRuntimeType(typeToCreateOrig))
				return typeToCreateOrig;

			Type result;
			if (_createdTypes.TryGetValue(typeToCreateOrig,out result))
				return result;
			foreach (Type item in _dependsOn[typeToCreateOrig])
				CreateType(item);

			TypeBuilder tb=_copiedTypes[typeToCreateOrig];
			FinalizeType(typeToCreateOrig,tb);

			if (typeToCreateOrig.IsNested)
				CreateType(typeToCreateOrig.DeclaringType);

			result=tb.CreateType();
			_createdTypes.Add(typeToCreateOrig,result);
			return result;
		}

		Type CopyTypeDefinition(Type typeToCopy)
		{
			if (typeToCopy==null)
				return null;

			TypeBuilder already;
			if (_copiedTypes.TryGetValue(typeToCopy,out already))
				return already;

			bool isGenericNonDefType=(typeToCopy.IsGenericType)&&(!typeToCopy.IsGenericTypeDefinition);
			bool isByRefType=(!typeToCopy.IsGenericType)&&(!typeToCopy.IsGenericTypeDefinition)&&(typeToCopy.IsByRef);
			bool breakRuntimeBlock=isGenericNonDefType||isByRefType;
			bool isRuntimeType=IsRuntimeType(typeToCopy);

			if ((!breakRuntimeBlock)&&(!isRuntimeType))
				return typeToCopy;

			#region special types
			/*if (typeToCopy.IsGenericParameter)
			{
				CopyTypeDefinition(typeToCopy.DeclaringType);
				foreach (Type item in typeToCopy.GetGenericArguments())
					CopyTypeDefinition(item);
			}*/

			if (typeToCopy.IsArray)
				CopyTypeDefinition(typeToCopy.GetElementType());

			if (isGenericNonDefType)
			{
				CopyTypeDefinition(typeToCopy.GetGenericTypeDefinition());
				foreach (Type item in typeToCopy.GetGenericArguments())
					CopyTypeDefinition(item);
			}

			if (isByRefType)
				CopyTypeDefinition(typeToCopy.GetElementType());
			#endregion special types

			Type[] interfaces=typeToCopy.GetInterfaces();
			Type[] interfaces2=Array.ConvertAll(interfaces,item => CopyTypeDefinition(item));

			Type baseType=typeof(object);
			if (typeToCopy.BaseType!=baseType)
				baseType=CopyTypeDefinition(typeToCopy.BaseType);

			if (!isRuntimeType)
				return typeToCopy;

			TypeBuilder result=typeToCopy.IsNested
				?((TypeBuilder)CopyTypeDefinition(typeToCopy.DeclaringType)).DefineNestedType(typeToCopy.Name,typeToCopy.Attributes,baseType,interfaces2)
				:_modBldr.DefineType((typeToCopy.FullName??typeToCopy.Name)/*.Replace("Jsil.Runtime.","JsilRun.")*/,typeToCopy.Attributes,baseType,interfaces2);
			_copiedTypes.Add(typeToCopy,result);

			Type[] genericArgs=typeToCopy.GetGenericArguments();
			if (genericArgs.Length!=0)
				result.DefineGenericParameters(Array.ConvertAll(genericArgs,item => item.Name));

			CopyAttributes(typeToCopy,result);

			return result;
		}

		void FinalizeType(Type origType,Type type)
		{
			if ((origType==null)||(!IsRuntimeType(origType))||(!(type is TypeBuilder))||(!phase2))
				return;

			TypeBuilder tb=(TypeBuilder)type;

			MemberInfo[] memsOrig=origType.GetMembers(_bindingFlags);
			foreach (MemberInfo item in memsOrig)
			{
				if (item.MemberType==MemberTypes.Field)
					CopyField((FieldInfo)item,tb);
				else if (item.MemberType==MemberTypes.Constructor)
					CopyConstructorDefinition((ConstructorInfo)item,tb);
				/*else if (item.MemberType==MemberTypes.Event)
					already=_copiedEvents.ContainsKey((EventInfo)item);*/
				else if (item.MemberType==MemberTypes.Method)
					CopyMethodDefinition((MethodInfo)item,tb);
				else if (item.MemberType==MemberTypes.Property)
					CopyPropertyDefinition((PropertyInfo)item,tb);
			}

			foreach (MemberInfo item in memsOrig)
			{
				if (item.MemberType==MemberTypes.Constructor)
				{
					ConstructorInfo ci=(ConstructorInfo)item;
					CopyConstructor(new KeyValuePair<ConstructorInfo,ConstructorBuilder>(ci,_copiedConstructors[ci]));
				}
				else if (item.MemberType==MemberTypes.Method)
				{
					MethodInfo mi=(MethodInfo)item;
					CopyMethod(new KeyValuePair<MethodInfo,MethodBuilder>(mi,_copiedMethods[mi]));
				}
			}
		}

		void FinalizeSpecialTypes()
		{
			foreach (KeyValuePair<Type,Type> item in _specialTypes)
			{
				Type type=item.Key;
				if (type.IsArray)
					FinalizeType(type.GetElementType(),item.Value.GetElementType());
				else if ((type.IsGenericType)&&(!type.IsGenericTypeDefinition))
				{
					FinalizeType(type.GetGenericTypeDefinition(),item.Value.GetGenericTypeDefinition());
					foreach (Type type2 in type.GetGenericArguments())
						FinalizeType(type2,GetTypeCopy(type2));
				}
				else if (item.Key.IsByRef)
					FinalizeType(type.GetElementType(),item.Value.GetElementType());
			}

			foreach (KeyValuePair<Type,Type> item in _specialTypes)
			{
				Type type=item.Key;
				if (type.IsArray)
					_createdTypes.Add(type,CreateType(type.GetElementType()).MakeArrayType());
				else if ((type.IsGenericType)&&(!type.IsGenericTypeDefinition))
					_createdTypes.Add(type,
						CreateType(type.GetGenericTypeDefinition())
							.MakeGenericType(Array.ConvertAll(type.GetGenericArguments(),item2 => CreateType(item2)))
							);
				else if (item.Key.IsByRef)
					_createdTypes.Add(type,CreateType(type.GetElementType()).MakeByRefType());
			}
		}

		bool IsRuntimeType(Type type)
		{
			return type.Assembly.GetName().Name=="Jsil.Runtime";
		}

		void CopyField(FieldInfo fieldToCopy,TypeBuilder typeBldr)
		{
			if (_copiedFields.ContainsKey(fieldToCopy))
				return;

			FieldBuilder result=typeBldr.DefineField(fieldToCopy.Name,GetTypeCopy(fieldToCopy.FieldType),fieldToCopy.Attributes);
			if ((int)(fieldToCopy.Attributes&FieldAttributes.HasDefault)!=0)
				result.SetConstant(fieldToCopy.GetRawConstantValue());

			_copiedFields.Add(fieldToCopy,result);
		}

		void CopyPropertyDefinition(PropertyInfo propertyToCopy,TypeBuilder typeBldr)
		{
			if (_copiedProperties.ContainsKey(propertyToCopy))
				return;

			PropertyBuilder result=typeBldr.DefineProperty(propertyToCopy.Name,propertyToCopy.Attributes,GetTypeCopy(propertyToCopy.PropertyType),Array.ConvertAll(propertyToCopy.GetIndexParameters(),item => GetTypeCopy(item.ParameterType)));
			if ((int)(propertyToCopy.Attributes&PropertyAttributes.HasDefault)!=0)
				result.SetConstant(propertyToCopy.GetRawConstantValue());

			if (propertyToCopy.CanRead)
				result.SetGetMethod(CopyMethodDefinition(propertyToCopy.GetGetMethod(true),typeBldr));
			if (propertyToCopy.CanWrite)
				result.SetSetMethod(CopyMethodDefinition(propertyToCopy.GetSetMethod(true),typeBldr));

			CopyAttributes(propertyToCopy,result);

			_copiedProperties.Add(propertyToCopy,result);
		}

		void CopyConstructorDefinition(ConstructorInfo constructorToCopy,TypeBuilder typeBldr)
		{
			if (_copiedConstructors.ContainsKey(constructorToCopy))
				return;

			ParameterInfo[] parms=constructorToCopy.GetParameters();
			Type[] parameterTypes=Array.ConvertAll(parms,item => GetTypeCopy(item.ParameterType));

			ConstructorBuilder result=typeBldr.DefineConstructor(constructorToCopy.Attributes,constructorToCopy.CallingConvention,parameterTypes);
			int a=0;
			foreach (ParameterInfo item in parms)
				result.DefineParameter(++a,item.Attributes,item.Name);
			result.SetImplementationFlags(constructorToCopy.GetMethodImplementationFlags());

			_copiedConstructors.Add(constructorToCopy,result);
			FindSpecialTypes(constructorToCopy);
		}

		MethodBuilder CopyMethodDefinition(MethodInfo methodToCopy,TypeBuilder typeBldr)
		{
			MethodBuilder already;
			if (_copiedMethods.TryGetValue(methodToCopy,out already))
				return already;

			MethodBuilder result=typeBldr.DefineMethod(methodToCopy.Name,methodToCopy.Attributes);

			string[] genericArgNames=Array.ConvertAll(methodToCopy.GetGenericArguments(),x => x.Name);
			GenericTypeParameterBuilder[] genericParameters=genericArgNames.Length==0?new GenericTypeParameterBuilder[0]:result.DefineGenericParameters(genericArgNames);
			Type[] genericParametersType=GetTypeCopy(methodToCopy.DeclaringType).GetGenericArguments();

			ParameterInfo[] parms=methodToCopy.GetParameters();
			Type[] parameterTypes=Array.ConvertAll(parms,item => item.ParameterType.IsGenericParameter?Array.Find(item.ParameterType.DeclaringMethod==null?genericParametersType:genericParameters,x => x.Name==item.ParameterType.Name):GetTypeCopy(item.ParameterType));
			result.SetParameters(parameterTypes);

			result.SetReturnType(methodToCopy.ReturnType.IsGenericParameter?Array.Find(methodToCopy.ReturnType.DeclaringMethod==null?genericParametersType:genericParameters,x => x.Name==methodToCopy.ReturnType.Name):GetTypeCopy(methodToCopy.ReturnType));

			MethodInfo interfaceDeclaration=FindMethodBase(methodToCopy);
			if (interfaceDeclaration!=null)
				typeBldr.DefineMethodOverride(result,interfaceDeclaration);

			int a=0;
			foreach (ParameterInfo item in parms)
				result.DefineParameter(++a,item.Attributes,item.Name);
			result.SetImplementationFlags(methodToCopy.GetMethodImplementationFlags());

			_copiedMethods.Add(methodToCopy,result);
			FindSpecialTypes(methodToCopy);

			CopyAttributes(methodToCopy,result);
			
			return result;
		}

		MethodInfo FindMethodBase(MethodInfo methodToCopy)
		{
			string name=methodToCopy.Name;
			int pos=methodToCopy.Name.LastIndexOf('.');
			if (pos==-1)
				return null;
			return Type.GetType(name.Substring(0,pos)).GetMethod(name.Substring(pos+1),_bindingFlags,null,methodToCopy.CallingConvention,Array.ConvertAll(methodToCopy.GetParameters(),x => x.ParameterType),null);
		}

		void CopyAttributes(object src,object target)
		{
			foreach (object item in ((MemberInfo)src).GetCustomAttributes(false))
			{
				Type itemType=item.GetType();
				CustomAttributeBuilder ab=null;
				if (itemType==typeof(System.Diagnostics.DebuggerHiddenAttribute))
					ab=new CustomAttributeBuilder(typeof(System.Diagnostics.DebuggerHiddenAttribute).GetConstructor(new Type[0]),new object[0]);
				else if (itemType==typeof(System.Diagnostics.DebuggerStepThroughAttribute))
					ab=new CustomAttributeBuilder(typeof(System.Diagnostics.DebuggerStepThroughAttribute).GetConstructor(new Type[0]),new object[0]);
				else if (itemType==typeof(System.Diagnostics.DebuggerDisplayAttribute))
				{
					Type t=typeof(System.Diagnostics.DebuggerDisplayAttribute);
					PropertyInfo[] props=t.GetProperties(BindingFlags.Instance|BindingFlags.Public|BindingFlags.SetProperty|BindingFlags.DeclaredOnly);
					props=Array.FindAll(props,x => x.CanWrite);
					object[] vals=Array.ConvertAll(props,x => x.GetValue(item,new object[0]));
					ab=new CustomAttributeBuilder(t.GetConstructor(new Type[] { typeof(string) }),new object[] { ((System.Diagnostics.DebuggerDisplayAttribute)item).Value },props,vals);
				}
				else if (itemType==typeof(System.Diagnostics.DebuggerTypeProxyAttribute))
					ab=new CustomAttributeBuilder(typeof(System.Diagnostics.DebuggerTypeProxyAttribute).GetConstructor(new Type[] { typeof(string) }),new object[] { TypeNameParser.Parse(((System.Diagnostics.DebuggerTypeProxyAttribute)item).ProxyTypeName).Name+", "+_modBldr.Assembly.FullName });
				else if (itemType==typeof(System.Diagnostics.DebuggerBrowsableAttribute))
					ab=new CustomAttributeBuilder(typeof(System.Diagnostics.DebuggerBrowsableAttribute).GetConstructor(new Type[] { typeof(System.Diagnostics.DebuggerBrowsableState) }),new object[] { ((System.Diagnostics.DebuggerBrowsableAttribute)item).State });
				else if (itemType==typeof(System.Reflection.DefaultMemberAttribute))
					ab=new CustomAttributeBuilder(typeof(System.Reflection.DefaultMemberAttribute).GetConstructor(new Type[] { typeof(string) }),new object[] { ((System.Reflection.DefaultMemberAttribute)item).MemberName });
				else if (itemType==typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute))
					ab=new CustomAttributeBuilder(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute).GetConstructor(new Type[0]),new object[0]);

				if (ab!=null)
				{
					TypeBuilder tb=target as TypeBuilder;
					if (tb==null)
					{
						MethodBuilder mb=target as MethodBuilder;
						if (mb==null)
							((PropertyBuilder)target).SetCustomAttribute(ab);
						else
							mb.SetCustomAttribute(ab);
					}
					else
						tb.SetCustomAttribute(ab);
				}
			}
		}

		void CopyConstructor(KeyValuePair<ConstructorInfo,ConstructorBuilder> constructorDefinition)
		{
			ConstructorBuilder cb=constructorDefinition.Value;
			CopyMethodBody(constructorDefinition.Key,() => cb.GetILGenerator(),(val) => cb.InitLocals=val);
		}

		void CopyMethod(KeyValuePair<MethodInfo,MethodBuilder> methodDefinition)
		{
			MethodBuilder mb=methodDefinition.Value;
			CopyMethodBody(methodDefinition.Key,() => mb.GetILGenerator(),(val) => mb.InitLocals=val);
		}

		void FindSpecialTypes(MethodBase method)
		{
			MethodBody methodBody=method.GetMethodBody();
			if (methodBody==null)
				return;

			IlReader.IlInstruction[] instructions=new IlReader().ReadMethod(methodBody,method);
			foreach (IlReader.IlInstruction instruction in instructions)
			{
				if (instruction.Data is FieldInfo)
				{
					FieldInfo fi=(FieldInfo)instruction.Data;
					GetTypeCopy(fi.DeclaringType);
					GetTypeCopy(fi.FieldType);
				}
				else if (instruction.Data is MethodInfo)
				{
					MethodInfo mi=(MethodInfo)instruction.Data;
					GetTypeCopy(mi.DeclaringType);
					GetTypeCopy(mi.ReturnType);
					foreach (ParameterInfo item in mi.GetParameters())
						GetTypeCopy(item.ParameterType);
				}
				else if (instruction.Data is Type)
					GetTypeCopy((Type)instruction.Data);
				else if (instruction.Data is ConstructorInfo)
				{
					ConstructorInfo ci=(ConstructorInfo)instruction.Data;
					GetTypeCopy(ci.DeclaringType);
					foreach (ParameterInfo item in ci.GetParameters())
						GetTypeCopy(item.ParameterType);
				}
			}
		}

		List<MethodBase> _copiedConstructorsBodies=new List<MethodBase>();
		List<MethodBase> _copiedMethodBodies=new List<MethodBase>();
		void CopyMethodBody(MethodBase methodToCopy,Func<ILGenerator> getGenDel,Action<bool> setInitLocalsDel)
		{
			//musi byt rozelene kvuli chybe v .NET 2.0 System.Reflection.RuntimeMethodInfo.Equals; v .NET 4.0 je opravena
			if (methodToCopy is MethodInfo)
			{
				if (_copiedMethodBodies.Contains(methodToCopy))
					return;
				_copiedMethodBodies.Add(methodToCopy);
			}
			else
			{
				if (_copiedConstructorsBodies.Contains(methodToCopy))
					return;
				_copiedConstructorsBodies.Add(methodToCopy);
			}

			MethodBody methodBodyToCopy=methodToCopy.GetMethodBody();
			if (methodBodyToCopy==null)
				return;

			ILGenerator gen=getGenDel();
			foreach (LocalVariableInfo item in methodBodyToCopy.LocalVariables)
			{
				Type t=item.LocalType;
				t=t.IsGenericParameter?Array.Find(t.DeclaringMethod==null?GetTypeCopy(t.DeclaringType).GetGenericArguments():GetMethodCopy((MethodInfo)t.DeclaringMethod).GetGenericArguments(),x => x.Name==t.Name):GetTypeCopy(t);
				gen.DeclareLocal(t,item.IsPinned);
			}
			setInitLocalsDel(methodBodyToCopy.InitLocals);

			WriteMethodInstructions(gen,new IlReader().ReadMethod(methodBodyToCopy,methodToCopy),methodBodyToCopy.ExceptionHandlingClauses);
		}

		void WriteMethodInstructions(ILGenerator gen,IlReader.IlInstruction[] instructions,IList<ExceptionHandlingClause> tryBlocks)
		{
			Dictionary<int,List<Label>> labels=new Dictionary<int,List<Label>>();

			List<Label> labelsOffset;
			foreach (IlReader.IlInstruction instruction in instructions)
			{
				if (labels.TryGetValue(instruction.Address,out labelsOffset))
					foreach (Label item in labelsOffset)
						gen.MarkLabel(item);

				foreach (ExceptionHandlingClause item in tryBlocks)
					if (item.TryOffset==instruction.Address)
						gen.BeginExceptionBlock();
				if ((instruction.Op==OpCodes.Leave)||(instruction.Op==OpCodes.Leave_S))
				{
					IlReader.IlInstruction next=instructions[instruction.InstructionIndex+1];
					bool nextHasFinallyBlock=false;
					foreach (ExceptionHandlingClause item in tryBlocks)
						if (item.HandlerOffset==next.Address)
							nextHasFinallyBlock=true;
					if (nextHasFinallyBlock)
					{
						if (instruction.Op==OpCodes.Leave)
							gen.BeginFinallyBlock();
						else
							ILGeneratorExt.BeginFinallyBlockShort(gen,(byte)instruction.Data);
						continue;
					}
				}
				else if (instruction.Op==OpCodes.Endfinally)
				{
					IlReader.IlInstruction next=instructions[instruction.InstructionIndex+1];
					bool nextHasEndExceptionBlock=false;
					foreach (ExceptionHandlingClause item in tryBlocks)
						if (item.HandlerOffset+item.HandlerLength==next.Address)
							nextHasEndExceptionBlock=true;
					if (nextHasEndExceptionBlock)
					{
						gen.EndExceptionBlock();
						continue;
					}
				}

				//Workaround for BeginFinallyBlockShort. BeginFinallyBlock produces only Leave instead of Leave.s and
				//possibly might prolong code and change jump targets. Can be workarounded with jump targets maintenance.
				//
				//This quick hack simply remove try/finally block but it left content. TryBlocks management has to be commented
				/*if (instruction.Op == OpCodes.Endfinally)
				{
					 gen.Emit(OpCodes.Nop);
					 continue;
				}*/

				if (instruction.Data==null)
					gen.Emit(instruction.Op);
				else if (instruction.Data is FieldInfo)
					gen.Emit(instruction.Op,GetFieldCopy((FieldInfo)instruction.Data));
				else if (instruction.Data is Int32)
					gen.Emit(instruction.Op,(Int32)instruction.Data);
				else if (instruction.Data is Int64)
					gen.Emit(instruction.Op,(Int64)instruction.Data);
				else if (instruction.Data is MethodInfo)
					gen.Emit(instruction.Op,GetMethodCopy((MethodInfo)instruction.Data));
				else if (instruction.Data is double)
					gen.Emit(instruction.Op,(double)instruction.Data);
				else if (instruction.Data is String)
					gen.Emit(instruction.Op,(String)instruction.Data);
				else if (instruction.Data is Type)
					gen.Emit(instruction.Op,GetTypeCopy((Type)instruction.Data));
				else if (instruction.Data is short)
					gen.Emit(instruction.Op,(short)instruction.Data);
				else if (instruction.Data is byte)
					gen.Emit(instruction.Op,(byte)instruction.Data);
				else if (instruction.Data is float)
					gen.Emit(instruction.Op,(float)instruction.Data);
				else if (instruction.Data is ConstructorInfo)
					gen.Emit(instruction.Op,GetConstructorCopy((ConstructorInfo)instruction.Data));
				else if (instruction.Op==OpCodes.Switch)
				{
					int[] labelsOffsets=(int[])instruction.Data;
					int a=0;
					Label[] nowLabels=new Label[labelsOffsets.Length];
					int offset=instructions[instruction.InstructionIndex+1].Address;
					foreach (int item in labelsOffsets)
					{
						if (!labels.TryGetValue(item+offset,out labelsOffset))
						{
							labelsOffset=new List<Label>();
							labels.Add(item+offset,labelsOffset);
						}
						Label l=gen.DefineLabel();
						nowLabels[a++]=l;
						labelsOffset.Add(l);
					}
					gen.Emit(instruction.Op,nowLabels);
				}
				else
					throw new NotImplementedException();
			}
		}
		#endregion copy

		#region IlReader
		internal class IlReader
		{
			private static readonly Dictionary<short,OpCode> _instructionLookup=new Dictionary<short,OpCode>();
			Module _module;
			bool _declaringTypeIsGeneric;
			Type[] _typeGenericArguments;
			bool _methodIsGeneric;
			Type[] _methodGenericArguments;

			static IlReader()
			{
				FillLookupTable();
			}

			internal IlInstruction[] ReadMethod(MethodBody methodBody,MethodBase methodBase)
			{
				_module=methodBase.Module;
				Type declaringType=methodBase.DeclaringType;
				_declaringTypeIsGeneric=declaringType.IsGenericType;
				_typeGenericArguments=_declaringTypeIsGeneric?declaringType.GetGenericArguments():null;
				_methodIsGeneric=methodBase.IsGenericMethod;
				_methodGenericArguments=_methodIsGeneric?methodBase.GetGenericArguments():null;

				List<IlInstruction> result=new List<IlInstruction>();

				byte[] instructionBytes=methodBody.GetILAsByteArray();
				int instructionIndex=0;
				int startAddress;
				for (int position=0;position<instructionBytes.Length;)
				{
					startAddress=position;
					short operationData=instructionBytes[position];
					if (IsInstructionPrefix(operationData))
						operationData=(short)((operationData<<8)|instructionBytes[++position]);

					position++;

					OpCode code;
					if (!_instructionLookup.TryGetValue(operationData,out code))
						throw new InvalidProgramException(string.Format("0x{0:X2} is not a valid op code.",operationData));

					int dataSize=GetSize(code.OperandType);
					byte[] data=new byte[dataSize];
					Buffer.BlockCopy(instructionBytes,position,data,0,dataSize);
					object objData=this.GetData(code,data);
					position+=dataSize;

					if (code.OperandType==OperandType.InlineSwitch)
					{
						dataSize=(int)objData;
						int[] labels=new int[dataSize];
						for (int index=0;index<labels.Length;index++)
						{
							labels[index]=BitConverter.ToInt32(instructionBytes,position);
							position+=4;
						}

						objData=labels;
					}

					result.Add(new IlInstruction(code,data,startAddress,objData,instructionIndex++));
				}

				return result.ToArray();
			}

			bool IsInstructionPrefix(short value)
			{
				return ((value&OpCodes.Prefix1.Value)==OpCodes.Prefix1.Value)||((value&OpCodes.Prefix2.Value)==OpCodes.Prefix2.Value)
					||((value&OpCodes.Prefix3.Value)==OpCodes.Prefix3.Value)||((value&OpCodes.Prefix4.Value)==OpCodes.Prefix4.Value)
					||((value&OpCodes.Prefix5.Value)==OpCodes.Prefix5.Value)||((value&OpCodes.Prefix6.Value)==OpCodes.Prefix6.Value)
					||((value&OpCodes.Prefix7.Value)==OpCodes.Prefix7.Value)||((value&OpCodes.Prefixref.Value)==OpCodes.Prefixref.Value);
			}

			private static int GetSize(OperandType operandType)
			{
				switch (operandType)
				{
					case OperandType.InlineNone:
						return 0;
					case OperandType.ShortInlineBrTarget:
					case OperandType.ShortInlineI:
					case OperandType.ShortInlineVar:
						return 1;
					case OperandType.InlineVar:
						return 2;
					case OperandType.InlineBrTarget:
					case OperandType.InlineField:
					case OperandType.InlineI:
					case OperandType.InlineMethod:
					case OperandType.InlineSig:
					case OperandType.InlineString:
					case OperandType.InlineSwitch:
					case OperandType.InlineTok:
					case OperandType.InlineType:
					case OperandType.ShortInlineR:
						return 4;
					case OperandType.InlineI8:
					case OperandType.InlineR:
						return 8;
					default:
						return 0;
				}
			}

			private object GetData(OpCode code,byte[] rawData)
			{
				object data=null;
				switch (code.OperandType)
				{
					case OperandType.InlineField:
						if ((_declaringTypeIsGeneric)||(_methodIsGeneric))
							data=_module.ResolveField(BitConverter.ToInt32(rawData,0),_typeGenericArguments,_methodGenericArguments);
						else
							data=_module.ResolveField(BitConverter.ToInt32(rawData,0));
						break;
					case OperandType.InlineSwitch:
						data=BitConverter.ToInt32(rawData,0);
						break;
					case OperandType.InlineBrTarget:
					case OperandType.InlineI:
						data=BitConverter.ToInt32(rawData,0);
						break;
					case OperandType.InlineI8:
						data=BitConverter.ToInt64(rawData,0);
						break;
					case OperandType.InlineMethod:
						if ((_declaringTypeIsGeneric)||(_methodIsGeneric))
							data=_module.ResolveMethod(BitConverter.ToInt32(rawData,0),_typeGenericArguments,_methodGenericArguments);
						else
							data=_module.ResolveMethod(BitConverter.ToInt32(rawData,0));
						break;
					case OperandType.InlineR:
						data=BitConverter.ToDouble(rawData,0);
						break;
					case OperandType.InlineSig:
						data=_module.ResolveSignature(BitConverter.ToInt32(rawData,0));
						break;
					case OperandType.InlineString:
						data=_module.ResolveString(BitConverter.ToInt32(rawData,0));
						break;
					case OperandType.InlineTok:
					case OperandType.InlineType:
						if ((_declaringTypeIsGeneric)||(_methodIsGeneric))
							data=_module.ResolveType(BitConverter.ToInt32(rawData,0),_typeGenericArguments,_methodGenericArguments);
						else
							data=_module.ResolveType(BitConverter.ToInt32(rawData,0));
						break;
					case OperandType.InlineVar:
						data=BitConverter.ToInt16(rawData,0);
						break;
					case OperandType.ShortInlineVar:
					case OperandType.ShortInlineI:
					case OperandType.ShortInlineBrTarget:
						data=rawData[0];
						break;
					case OperandType.ShortInlineR:
						data=BitConverter.ToSingle(rawData,0);
						break;
				}
				return data;
			}

			static void FillLookupTable()
			{
				// Might be better to do an array lookup.  Use a seperate arrary for instructions without a prefix and array for each prefix.
				FieldInfo[] fields=typeof(OpCodes).GetFields(BindingFlags.Static|BindingFlags.Public);
				foreach (FieldInfo field in fields)
				{
					OpCode code=(OpCode)field.GetValue(null);
					_instructionLookup.Add(code.Value,code);
				}
			}

			[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
			internal struct IlInstruction
			{
				private readonly OpCode operationCode; // 40.  56-64.  The entire structure is very big.  maybe do array lookup for opcode instead.
				private readonly byte[] instructionRawData;
				private readonly object instructionData;
				private readonly int instructionAddress;
				private readonly int index;

				internal IlInstruction(OpCode code,byte[] instructionRawData,int instructionAddress,object instructionData,int index)
				{
					this.operationCode=code;
					this.instructionRawData=instructionRawData;
					this.instructionAddress=instructionAddress;
					this.instructionData=instructionData;
					this.index=index;
				}

				public OpCode Op { get { return this.operationCode; } }
				public byte[] RawData { get { return this.instructionRawData; } }
				public object Data { get { return this.instructionData; } }
				public int Address { get { return this.instructionAddress; } }
				public int InstructionIndex { get { return this.index; } }

				public int DataValue
				{
					get
					{
						if (this.Data!=null)
						{
							if (this.Data is byte)
								return (byte)this.Data;
							else if (this.Data is short)
								return (short)this.Data;
							else if (this.Data is int)
								return (int)this.Data;
						}
						return 0;
					}
				}

				public int Length { get { return this.Op.Size+(this.RawData==null?0:this.RawData.Length); } }

				#if DEBUG
				public override string ToString()
				{
					StringBuilder builder=new StringBuilder();
					builder.AppendFormat("0x{0:x4} {1,-10}",this.Address,this.Op.Name);

					if (this.Data!=null)
						builder.Append(this.Data.ToString());

					if (this.RawData!=null&&this.RawData.Length>0)
					{
						builder.Append(" [0x");
						for (int i=this.RawData.Length-1;i>=0;i--)
							builder.Append(this.RawData[i].ToString("x2",System.Globalization.CultureInfo.InvariantCulture));
						builder.Append(']');
					}

					return builder.ToString();
				}
				#endif
			}
		}
		#endregion IlReader

		Dictionary<Type,Type> _specialTypes=new Dictionary<Type,Type>();
		internal Type GetTypeCopy(Type type)
		{
			Type result2;
			if (_createdTypes.TryGetValue(type,out result2))
				return result2;
			if (_specialTypes.TryGetValue(type,out result2))
				return result2;
			TypeBuilder result;
			if (_copiedTypes.TryGetValue(type,out result))
				return result;

			bool isGenericNonDefType=(type.IsGenericType)&&(!type.IsGenericTypeDefinition);
			bool isByRefType=(!type.IsGenericType)&&(!type.IsGenericTypeDefinition)&&(type.IsByRef);
			bool breakRuntimeBlock=isGenericNonDefType||isByRefType;
			bool isRuntimeType=IsRuntimeType(type);

			if ((!breakRuntimeBlock)&&(!isRuntimeType))
				return type;

			#region special types
			//all special type should be in _createdTypes already
			if (type.IsArray)
				return AddSpecial(type,GetTypeCopy(type.GetElementType()).MakeArrayType());

			if (isGenericNonDefType)
				return AddSpecial(type,
					GetTypeCopy(type.GetGenericTypeDefinition())
						.MakeGenericType(Array.ConvertAll(type.GetGenericArguments(),item => GetTypeCopy(item)))
						);

			if (isByRefType)
				return AddSpecial(type,GetTypeCopy(type.GetElementType()).MakeByRefType());
			#endregion special types

			return type;
		}

		Type AddSpecial(Type typeToCopy,Type typeCopy)
		{
			_specialTypes.Add(typeToCopy,typeCopy);
			return typeCopy;
		}

		internal ConstructorInfo GetConstructorCopy(ConstructorInfo ci)
		{
			ConstructorBuilder result;
			if (_copiedConstructors.TryGetValue(ci,out result))
				return result;

			BindingFlags bindingFlags=(ci.IsStatic?BindingFlags.Static:BindingFlags.Instance)|(ci.IsPublic?BindingFlags.Public:BindingFlags.NonPublic)|BindingFlags.DeclaredOnly;
			Type typeCopy;
			if (_createdTypes.TryGetValue(ci.DeclaringType,out typeCopy))
				return typeCopy.GetConstructor(bindingFlags,null,ci.CallingConvention,Array.ConvertAll(ci.GetParameters(),item => GetTypeCopy(item.ParameterType)),null);

			return ci;
		}

		internal MethodInfo GetMethodCopy(MethodInfo mi)
		{
			MethodBuilder result;
			if (_copiedMethods.TryGetValue(mi,out result))
				return result;

			if ((mi.IsGenericMethod)&&(!mi.IsGenericMethodDefinition))
				return GetMethodCopy(mi.GetGenericMethodDefinition()).MakeGenericMethod(Array.ConvertAll(mi.GetGenericArguments(),item => GetTypeCopy(item)));

			BindingFlags bindingFlags=(mi.IsStatic?BindingFlags.Static:BindingFlags.Instance)|(mi.IsPublic?BindingFlags.Public:BindingFlags.NonPublic)|BindingFlags.DeclaredOnly;
			Type declType=mi.DeclaringType;
			if (IsRuntimeType(declType))
			{
				GetTypeCopy(declType);
				return CreateType(declType).GetMethod(mi.Name,bindingFlags,null,mi.CallingConvention,Array.ConvertAll(mi.GetParameters(),item => GetTypeCopy(item.ParameterType)),null);
			}

			if (declType.IsGenericType&&(!declType.IsGenericTypeDefinition))
				return GetTypeCopy(declType)
					.GetMethod(mi.Name,bindingFlags,null,mi.CallingConvention,Array.ConvertAll(mi.GetParameters(),item => GetTypeCopy(item.ParameterType)),null);

			return mi;
		}

		internal FieldInfo GetFieldCopy(FieldInfo fi)
		{
			FieldBuilder result;
			return _copiedFields.TryGetValue(fi,out result)?result:fi;
		}

		bool IsDelegateDescendant(Type type)
		{
			return (typeof(Delegate).IsAssignableFrom(type))&&(typeof(Delegate)!=type);
		}

		class TypeNameParser
		{
			public string Name { get; private set; }
			public AssemblyName AssemblyName { get; private set; }

			TypeNameParser()
			{}

			public static TypeNameParser Parse(string name)
			{
				TypeNameParser result=new TypeNameParser();
				var pos=name.IndexOf(',');
				if (pos>0)
				{
					result.Name=name.Substring(0,pos).Trim();
					result.AssemblyName=new AssemblyName(name.Substring(pos+1).Trim());
				}
				else
					result.Name=name;
				return result;
			}
		}
	}

	static class ILGeneratorExt
	{
		internal static void BeginFinallyBlockShort(ILGenerator gen,byte offset)
		{
			Type ilGenType=typeof(ILGenerator);
			int m_currExcStackCount=(int)ilGenType.GetField("m_currExcStackCount",BindingFlags.NonPublic|BindingFlags.Instance).GetValue(gen);
			if (m_currExcStackCount==0)
				gen.BeginFinallyBlock(); //raise original exception
			object _ExceptionInfo=((object[])ilGenType.GetField("m_currExcStack",BindingFlags.NonPublic|BindingFlags.Instance).GetValue(gen))[m_currExcStackCount-1];
			int currentState=(int)_ExceptionInfo.GetType().GetMethod("GetCurrentState",BindingFlags.NonPublic|BindingFlags.Instance).Invoke(_ExceptionInfo,new object[0]);
			Label endLabel=(Label)_ExceptionInfo.GetType().GetMethod("GetEndLabel",BindingFlags.NonPublic|BindingFlags.Instance).Invoke(_ExceptionInfo,new object[0]);
			int num=0;
			if (currentState!=0)
			{
				gen.Emit(OpCodes.Leave_S,endLabel);
				num=(int)ilGenType.GetField("m_length",BindingFlags.NonPublic|BindingFlags.Instance).GetValue(gen);
			}
			gen.MarkLabel(endLabel);

			Label label=gen.DefineLabel();
			_ExceptionInfo.GetType().GetMethod("SetFinallyEndLabel",BindingFlags.NonPublic|BindingFlags.Instance).Invoke(_ExceptionInfo,new object[] { label });
			gen.Emit(OpCodes.Leave_S,label);
			int m_length=(int)ilGenType.GetField("m_length",BindingFlags.NonPublic|BindingFlags.Instance).GetValue(gen);
			if (num==0)
				num=m_length;
			_ExceptionInfo.GetType().GetMethod("MarkFinallyAddr",BindingFlags.NonPublic|BindingFlags.Instance).Invoke(_ExceptionInfo,new object[] { m_length,num });
		}
	}
}
